package com.yet.source;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;

import com.alibaba.fastjson.util.TypeUtils;
import com.yet.center.ConfigCenter;
import com.yet.domain.BeanField;
import com.yet.exception.ConfigException;
import com.yet.listener.YetListener;
import com.yet.listener.YetPathEvent;
import com.yet.serialization.Serialization;

public class SourceAutowireManager
		implements SourceAutowirePostProcessor, YetListener, CommandLineRunner, ApplicationContextAware {

	private static Logger logger = LoggerFactory.getLogger(SourceAutowireManager.class);

	private static final ConcurrentMap<String, List<BeanField>> config2BeanNameMap = new ConcurrentHashMap<String, List<BeanField>>();

	private ApplicationContext context;

	private String profile;

	private Serialization serialization;

	@Override
	public void postProcessAfterCenterAutowire(CenterPropertyValues<Map<String, Object>> centerPropertyValues,
			Object bean, String beanName) throws ConfigException {
		centerPropertyValues.getKeyPropertyValue().forEach(p -> {
			Optional.ofNullable(config2BeanNameMap.get(p.getKey())).orElseGet(() -> {
				ArrayList<BeanField> arrayList = new ArrayList<BeanField>();
				config2BeanNameMap.putIfAbsent(p.getKey(), arrayList);
				return arrayList;
			}).add(new BeanField(beanName, p.getName()));
		});
	}

	@Override
	public void onApplicationEvent(YetPathEvent event) {
		logger.info("Refresh:{}", event);
		ChildData data = event.getData();
		List<BeanField> list = config2BeanNameMap.get(ConfigCenter.extractKey(profile, data.getPath()));
		if (list != null && list.size() > 0) {
			list.forEach(b -> {
				refreshBeanField(b, serialization.deserialize(data.getData(), String.class));
			});
		}
	}

	@Override
	public void run(String... args) throws Exception {

	}

	public void refreshBeanField(final BeanField beanField, final Object value) {
		Object bean = context.getBean(beanField.getBeanName());
		BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
		PropertyDescriptor propertyDescriptor = null;
		PropertyDescriptor[] propertyDescriptors = beanWrapper.getPropertyDescriptors();
		if (propertyDescriptors != null && propertyDescriptors.length > 0) {
			for (PropertyDescriptor item : propertyDescriptors) {
				if (beanField.getFileldName().equals(item.getName())) {
					propertyDescriptor = item;
				}
			}
		}
		if (propertyDescriptor != null && propertyDescriptor.getWriteMethod() != null) {
			beanWrapper.setPropertyValue(beanField.getFileldName(), value);
			logger.info("RefreshBeanField[set] success, {}#{}:{}", beanField.getBeanName(), beanField.getFileldName(),
					value);
		} else {
			final Object finalBean = bean;
			ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
				@Override
				public void doWith(Field fieldItem) throws IllegalArgumentException, IllegalAccessException {
					if (fieldItem.getName().equals(beanField.getFileldName())) {
						Object valueObj = TypeUtils.cast(value, fieldItem.getType(), null);
						ReflectionUtils.makeAccessible(fieldItem);
						ReflectionUtils.setField(fieldItem, finalBean, valueObj);
						logger.info("RefreshBeanField[field] success, {}#{}:{}", beanField.getBeanName(),
								beanField.getFileldName(), value);
					}
				}
			});
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = applicationContext;
		this.serialization = applicationContext.getBean(Serialization.class);
		this.profile = StringUtils
				.isNotBlank((this.profile = applicationContext.getEnvironment().getProperty(ConfigCenter.PROP_PROFILE)))
						? this.profile : ConfigCenter.DEFAULT_PROFILE;
	}

	public String getProfile() {
		return profile;
	}

	public void setProfile(String profile) {
		this.profile = profile;
	}

}
